Skip to content

feat(xai): add TTS connection pooling#1977

Open
rosetta-livekit-bot[bot] wants to merge 1 commit into
mainfrom
audio-elides-fondu
Open

feat(xai): add TTS connection pooling#1977
rosetta-livekit-bot[bot] wants to merge 1 commit into
mainfrom
audio-elides-fondu

Conversation

@rosetta-livekit-bot

@rosetta-livekit-bot rosetta-livekit-bot Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Summary

  • add xAI TTS support using pooled WebSocket connections
  • invalidate pooled connections when xAI TTS connection options change
  • export xAI TTS types from the plugin entrypoint

Verification

  • pnpm --filter @livekit/agents-plugin-xai build
  • pnpm --filter @livekit/agents-plugin-xai lint (passes with existing _utils.ts warnings)
  • pnpm build
  • cue-cli validation attempted with a temporary xAI TTS voice agent; dispatch reached the worker, but runtime TTS validation could not complete because XAI_API_KEY was not available in the environment

Ported from livekit/agents#6317

Original PR description

Summary

  • Add a ConnectionPool to the xAI TTS plugin so streamed synthesis segments can reuse websocket connections.
  • Expose prewarm() through the pool and close pooled connections from aclose().
  • Invalidate pooled connections when websocket URL options change.
  • Add a plugin regression test that verifies two synthesized segments reuse one websocket and close it only when the TTS instance is closed.

Fixes #6078

Root cause

The xAI TTS stream opened a websocket with _connect_ws() for each synthesized segment and always closed it in _run_ws() cleanup. Unlike other websocket-backed TTS plugins, it did not use the shared utils.ConnectionPool, so repeated streamed segments could not reuse an existing connection or be prewarmed.

Notes

xAI documents the streaming TTS websocket as multi-utterance: after audio.done, clients can send another text.delta / text.done sequence on the same connection.

Test plan

  • uv run pytest tests/test_plugin_xai_tts.py --plugin xai -q
  • uv run pytest tests/test_realtime/test_xai_realtime_model.py --unit -q
  • uv run ruff check tests/test_plugin_xai_tts.py livekit-plugins/livekit-plugins-xai/livekit/plugins/xai/tts.py
  • uv run ruff format --check tests/test_plugin_xai_tts.py livekit-plugins/livekit-plugins-xai/livekit/plugins/xai/tts.py
  • uv run --group typing mypy --untyped-calls-exclude=smithy_aws_core -p livekit.plugins.xai

@rosetta-livekit-bot rosetta-livekit-bot Bot requested a review from a team as a code owner July 7, 2026 00:31
@changeset-bot

changeset-bot Bot commented Jul 7, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 01c2244

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 36 packages
Name Type
@livekit/agents-plugin-xai Patch
@livekit/agents Patch
@livekit/agents-plugin-anam Patch
@livekit/agents-plugin-anthropic Patch
@livekit/agents-plugin-assemblyai Patch
@livekit/agents-plugin-baseten Patch
@livekit/agents-plugin-bey Patch
@livekit/agents-plugin-cartesia Patch
@livekit/agents-plugin-cerebras Patch
@livekit/agents-plugin-deepgram Patch
@livekit/agents-plugin-did Patch
@livekit/agents-plugin-elevenlabs Patch
@livekit/agents-plugin-fishaudio Patch
@livekit/agents-plugin-google Patch
@livekit/agents-plugin-hedra Patch
@livekit/agents-plugin-hume Patch
@livekit/agents-plugin-inworld Patch
@livekit/agents-plugin-lemonslice Patch
@livekit/agents-plugin-liveavatar Patch
@livekit/agents-plugin-livekit Patch
@livekit/agents-plugin-minimax Patch
@livekit/agents-plugin-mistral Patch
@livekit/agents-plugin-mistralai Patch
@livekit/agents-plugin-neuphonic Patch
@livekit/agents-plugin-openai Patch
@livekit/agents-plugin-perplexity Patch
@livekit/agents-plugin-phonic Patch
@livekit/agents-plugin-resemble Patch
@livekit/agents-plugin-rime Patch
@livekit/agents-plugin-runway Patch
@livekit/agents-plugin-sarvam Patch
@livekit/agents-plugin-silero Patch
@livekit/agents-plugin-soniox Patch
@livekit/agents-plugin-tavus Patch
@livekit/agents-plugin-trugen Patch
@livekit/agents-plugins-test Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 3 potential issues.

Open in Devin Review

Comment thread plugins/xai/src/tts.ts
const XAI_WEBSOCKET_URL = 'wss://api.x.ai/v1/tts';
const DEFAULT_VOICE = 'ara';

export type GrokVoices = 'Ara' | 'Eve' | 'Leo' | 'Rex' | 'Sal';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Default voice name doesn't match the declared voice type, misleading users into sending wrong casing to the API

The default voice is set to lowercase 'ara' (DEFAULT_VOICE at plugins/xai/src/tts.ts:25) but the GrokVoices type declares capitalized names ('Ara' | 'Eve' | 'Leo' | 'Rex' | 'Sal'), so users relying on type hints will send capitalized values that may be rejected by the API.

Impact: Users who pick a voice from the GrokVoices type (e.g. 'Ara') may get API errors or unexpected behavior if the xAI TTS endpoint requires lowercase names.

Inconsistency with the existing realtime module's voice type

The existing realtime module at plugins/xai/src/realtime/realtime_model.ts:23 defines GrokVoices = 'eve' | 'ara' | 'rex' | 'sal' | 'leo' (all lowercase), and its DEFAULT_VOICE = 'ara' matches. The new TTS module's GrokVoices type uses capitalized names while its DEFAULT_VOICE is lowercase 'ara', creating an internal contradiction. Since the voice value is passed directly as a URL query parameter at plugins/xai/src/tts.ts:438 (url.searchParams.set('voice', opts.voice)), the casing matters for the API call.

Suggested change
export type GrokVoices = 'Ara' | 'Eve' | 'Leo' | 'Rex' | 'Sal';
export type GrokVoices = 'ara' | 'eve' | 'leo' | 'rex' | 'sal';
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment thread plugins/xai/src/tts.ts
void messages.close();
}
},
{ signal: this.abortSignal },

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 ChunkedStream does not pass timeout to withConnection

At plugins/xai/src/tts.ts:424, the ChunkedStream calls withConnection with only { signal: this.abortSignal } and no timeout. This means the connection acquisition uses the pool's default connectTimeout of 10 seconds (from agents/src/connection_pool.ts:73). In contrast, the SynthesizeStream at line 321 passes this.connOptions.timeoutMs. This inconsistency means the chunked stream ignores user-configured timeout for connection establishment, though the default 10s is reasonable.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment thread plugins/xai/src/index.ts

export * as realtime from './realtime/index.js';
export { STT, type STTOptions, type STTLanguages } from './stt.js';
export { TTS, type GrokVoices, type TTSLanguages, type TTSOptions } from './tts.js';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 Two conflicting GrokVoices types exported from the same package

The package now exports two different GrokVoices types: one from tts.ts (capitalized: 'Ara' | 'Eve' | ...) at the top level, and one from realtime/realtime_model.ts (lowercase: 'eve' | 'ara' | ...) under the realtime namespace. While they don't technically conflict (different export paths), users importing both would encounter confusing type differences for the same concept. This should be unified.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants